package cn.smile.auth.filters;

import cn.smile.auth.service.LoginService;
import cn.smile.bean.form.log.ban.BanAddressAddForm;
import cn.smile.bean.form.login.LoginInfoForm;
import cn.smile.business.core.ISysUserService;
import cn.smile.business.log.IBanAddressService;
import cn.smile.business.log.IIpInfoService;
import cn.smile.business.log.ILogInfoService;
import cn.smile.commons.constant.CommonConstant;
import cn.smile.commons.constant.NumberConstant;
import cn.smile.commons.constant.RedisKeyConstant;
import cn.smile.commons.response.MyResponseCode;
import cn.smile.commons.response.MyResult;
import cn.smile.utils.*;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import jakarta.annotation.Resource;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author smile-jt
 */
@Slf4j
@Component
@WebFilter(urlPatterns = {"/**"}, filterName = "myFilter")
public class MyFilter implements Filter {
    private static final String DEFAULT_URL_MATCHER = "";
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private ILogInfoService logService;
    @Resource
    private IIpInfoService ipService;
    @Resource
    private IBanAddressService banService;
    @Resource
    private ISysUserService userService;
    @Resource
    private LoginService loginService;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
        log.info("[MyFilter].[init] ------> 拦截器初始化 结束");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("[MyFilter].[doFilter] ------> 拦截器执行开始");
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        String uri = req.getRequestURI();
        String method = req.getMethod();
        String ip = IpUtils.getRemoteIp(req);
        String token = req.getHeader(CommonConstant.HEADER_TOKEN_KEY);
        log.info("[{}] 发起了 '{}' 请求, 请求路径: {}", ip, method, uri);
        //添加访问日志记录
        boolean check = this.checkIp(ip, uri, token, resp);
        if (check && this.checkUriAndToken(uri, token, resp)) {
            //刷新最后操作时间
            LoginInfoForm form = new LoginInfoForm();
            form.setLastLoginAddress(ip);
            form.setId(UserUtil.getUserInfo().getId());
            userService.updateLoginTime(form);
            //刷新登录有效时间
            loginService.refreshEff();
            //运行请求继续
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
        log.info("[MyFilter].[destroy] ------> 拦截器销毁 结束");
    }

    /**
     * 检查是否属于限制IP访问
     *
     * @param ip       ip地址
     * @param uri      请求URI
     * @param token    用户TOKEN
     * @param response 请求返回对象
     */
    private boolean checkIp(String ip, String uri, String token, HttpServletResponse response) {
        this.writeIpInfo(ip);
        Object bucket = redisUtil.get(RedisKeyConstant.BAN_ACCESS_ADDRESS);
        boolean allow = Boolean.TRUE;
        if (!ObjectUtils.isEmpty(bucket)) {
            List<String> dataList = JSON.parseArray(bucket.toString(), String.class);
            if (!CollectionUtils.isEmpty(dataList)) {
                if (dataList.contains(ip)) {
                    setResultJson(response, MyResponseCode.BAN_IP_ACCESS);
                    allow = false;
                }
            }
        }
        if (allow) {
            bucket = redisUtil.get(String.format(RedisKeyConstant.SUSPEND_VISIT_IP_NUM_KEY, ip));
            if (!ObjectUtils.isEmpty(bucket)) {
                int num = (Integer) bucket;
                if (num > NumberConstant.FIVE) {
                    setResultJson(response, MyResponseCode.SUSPEND_VISIT_IP_ERROR);
                    allow = false;
                }
            }
        }
        LogAddUtil.addAccessInfo(ip, uri, token, allow, logService);
        return allow;
    }

    /**
     * 写入请求IP信息
     *
     * @param ip 请求IP地址
     */
    private void writeIpInfo(String ip) {
        Boolean isOverseas = ipService.checkIpAdd(ip);
        if (isOverseas) {
            //国外IP, 添加禁止访问记录
            BanAddressAddForm form = new BanAddressAddForm();
            form.setAddress(ip);
            banService.addInfo(form);
            //刷新Redis信息
            banService.writeIpToRedis();
        }
    }

    /**
     * 检查访问URI和token有效性
     *
     * @param uri      请求URI
     * @param token    用户TOKEN
     * @param response 请求返回对象
     */
    private boolean checkUriAndToken(String uri, String token, HttpServletResponse response) {
        //校验是否忽略鉴权
        Set<String> ignoreUrls = this.getIgnoreUrls();
        for (String ignoreUrl : ignoreUrls) {
            UrlMatcherUtil matcher = new UrlMatcherUtil(ignoreUrl, DEFAULT_URL_MATCHER);
            if (matcher.matches(uri)) {
                return Boolean.TRUE;
            }
        }
        //校验 Header 中 Token 是否存在
        if (ObjectUtils.isEmpty(token)) {
            setResultJson(response, MyResponseCode.USER_NOT_LOGGED_IN);
            return Boolean.FALSE;
        }
        //校验 Redis 中 Token 是否存在
        Object bucket = redisUtil.get(String.format(RedisKeyConstant.USER_TOKEN_KEY, token));
        if (ObjectUtils.isEmpty(bucket)) {
            setResultJson(response, MyResponseCode.USER_NOT_LOGGED_IN);
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    /**
     * 获取返回对象
     *
     * @param response 返回对象
     */
    private static void setResultJson(HttpServletResponse response, MyResponseCode code) {
        MyResult<Boolean> result = MyResult.error(code, false);
        response.setContentType(CommonConstant.CONTENT_TYPE);
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        try {
            PrintWriter out = response.getWriter();
            out.write(JSON.toJSONString(result));
            out.flush();
            out.close();
        } catch (Exception e) {
            log.error("写入返回对象失败, e: ", e);
        }
    }

    /**
     * 获取忽略权限URI集合
     *
     * @return uri集合
     */
    private Set<String> getIgnoreUrls() {
        Set<String> uris = new HashSet<>(NumberConstant.EIGHT);
        Object bucket = redisUtil.get(RedisKeyConstant.IGNORE_URLS_KEY);
        if (!ObjectUtils.isEmpty(bucket)) {
            List<String> cacheUris = JSON.parseArray(bucket.toString(), String.class);
            if (!CollectionUtils.isEmpty(cacheUris)) {
                uris.addAll(cacheUris);
            }
        }

        return uris;
    }
}
